/**
 * @file spi.c
 * @brief T1 SPI Controller Driver
 *
 * @author yemt@witsi.cn
 * @date 2011-08-12
 * @version T1.SPI.01
 */
#include "regs.h"
#include "spi.h"
#include "dev.h"
#include "init.h"
#include "config.h"

/**
 * SPI Controller Parameter Structure
 */
typedef struct {
        spi_reg_t *reg;         /**< SPI Controller Register */
        uint8_t sub_id;         /**< Current Slave ID */
        uint8_t sub_nr;         /**< The Slave Numbers */
}t1_spi_bus_t;

/**
 * SPI Controller Registered
 */
static t1_spi_bus_t t1spi[] = {
#ifdef CONFIG_T1_SPI0
        {
                .reg = SPI0,
                .sub_id = -1,
                .sub_nr = 0
        },
#endif
#ifdef CONFIG_T1_SPI1
        {
                .reg = SPI1,
                .sub_id = -1,
                .sub_nr = 0
        },
#endif
};

/**
 * @fn spi_register_slave
 * @brief Register the slave with a initial configuration
 *
 * @param slave         Point to the slave to be registered
 * @param param         Point to the slave initial configuration
 * @return
 *      -1              Failed
 *      0               Success
 */
int spi_register_slave(spi_slave_t *slave, spi_param_t *param)
{
        uint32_t reg_data;
        if (param->bus_16bit) { /* only 8 bit supported */
                return -1;
        }

        reg_data = SPI_CHAR_8BIT;
        if (param->cpol)
                reg_data |= SPI_CKPL;
        if (!(param->cpha))
                reg_data |= SPI_CKPH; /* TI Mode */
        if (param->lsb_mod)
                reg_data |= SPI_LSB_FIRST;

        if (param->baud > CONFIG_PCLK1 >> 1)
                param->baud = CONFIG_PCLK1 >> 1;
        reg_data |= ((CONFIG_PCLK1 / param->baud) << 16);

        slave->priv = reg_data;
        slave->sub_id = t1spi[slave->bus].sub_nr ++;

        return 0;
}

#define LOW16BITS(w) ((w) & 0x0000FFFF)
#define HIGH16BITS(w) (((w) >> 16) & 0x0000FFFF)

/**
 * @fn spi_setup_slave
 * @brief Configure the slave when switching
 *
 * @param slave         Point to the slave to be registered
 * @return
 *      0               Success
 */
static inline int spi_setup_slave(spi_slave_t * spi)
{
        if (t1spi[spi->bus].sub_id != spi->sub_id) {
                t1spi[spi->bus].reg->CCTL = LOW16BITS(spi->priv);
                t1spi[spi->bus].reg->SPBRG = HIGH16BITS(spi->priv);
                t1spi[spi->bus].sub_id = spi->sub_id;
        }
        return 0;
}

/**
 * @fn spi_xfer
 * @brief SPI data exchanging
 *
 * @param slave         Point to the slave to be registered
 * @param dout          The data to be sent
 * @param len           The size of the data to be sent
 * @param din           Storing the data received
 * @param flags         SPI chip selection
 * @return
 *      0               Success
 */
int spi_xfer(spi_slave_t * spi, const void *dout, int len,
                void *din, uint32_t flags)
{
        int tx;
        spi_reg_t *reg = t1spi[spi->bus].reg;

        spi_setup_slave(spi);

        if (flags & SPI_XFER_BEGIN) {
                if ( spi->ctl_cs)
                    spi->ctl_cs(0);
        }


        if (dout)  {
                uint8_t * pout8 = (uint8_t *)dout;
                reg->GCTL |= SPI_TX_EN;
                for (tx = 0; tx < len; tx ++) {
                        while (reg->CSTAT & SPI_CSTAT_TX_FULL);
                        reg->TXREG = *pout8 ++;
                }
                while (!(reg->CSTAT & SPI_CSTAT_TX_EPT));
                reg->GCTL &= ~SPI_TX_EN;
        }
        else if (din) {
                uint8_t * pin8 = (uint8_t *)din;
                reg->RXDNR = len;
                reg->GCTL |= SPI_RX_EN;
                for (tx = 0; tx < len; tx ++) {
                        while (!(reg->CSTAT & SPI_CSTAT_RX_AVL));
                        *pin8 ++ = reg->RXREG;
                }
                reg->GCTL &= ~SPI_RX_EN;
        }

        if (flags & SPI_XFER_END) {
                if (spi->ctl_cs)
                    spi->ctl_cs(1);
        }
        return 0;
}

/**
 * @fn t1_spi_init
 * @brief Configure the slave when switching
 *
 * @return
 *      0               Success
 */
static int t1_spi_init(void)
{
        int n = sizeof (t1spi) / sizeof (t1_spi_bus_t);
        int i = 0;
        spi_reg_t *reg;

        for (; i < n; i ++) {
                reg = t1spi[i].reg;
                reg->GCTL = SPI_EN | SPI_SEL_MASTER;
                reg->INTEN = 0;
                reg->INTCLR = 0x7F;
                reg->SCSR = 0xFF;
        }
        return 0;
}

arch_initcall(t1_spi_init);
